/*
 * Copyright (c) 2007, TopCoder, Inc. All rights reserved.
 */

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using TopCoder.Util.Calendar.Entry;

namespace TopCoder.WinForms.Controls.CalendarView.Views
{
    /// <summary>
    /// A type of <see cref="PopupPanel"/> that display all info in a day,
    /// in the same manner as the display panel in Day View.
    /// Used in quarterly and year views when a mouse moves over a day entry.
    /// The <see cref="Columns"/> property will determine which columns will appear in the popup.
    /// All data will not be truncated and shown in its entirety.
    /// </summary>
    /// <remarks>Thread Safety: This class is mutable and not thread-safe.</remarks>
    /// <author>Ghostar</author>
    /// <author>yext</author>
    /// <author>argolite</author>
    /// <author>alex.dolin</author>
    /// <version>1.1</version>
    /// <copyright>Copyright (c) 2007, TopCoder, Inc. All rights reserved.</copyright>
    public class FullDayInfoPopupPanel : PopupPanel
    {
        /// <summary>
        /// Represents the entry for this panel. It will not be null, empty or contain null elements.
        /// Set in the contrustor and will never change. Accessed via property.
        /// </summary>
        private readonly IList<CalendarEntry> entries;

        /// <summary>
        /// <para>Represents the calendar entries sorted by type.</para>
        /// </summary>
        private IDictionary<CalendarEntryType, IList<CalendarEntry>> sortedEntries;

        /// <summary>
        /// Represents the names of columns to display. A null, by default, means all available columns will be used
        /// as they are found in the entry. An empty list means no columns will be displayed, and any given list will
        /// indicate the desired columns. If a given column is not found in the entry, it is ignored.
        /// The list is managed in the Columns property.
        /// </summary>
        private IList<string> columns;

        /// <summary>
        /// Names of the columns to display. If columns is null then
        /// this field will hold default columns.
        /// </summary>
        private IList<string> viewColumns;

        /// <summary>
        /// Measured height of the normal row.
        /// </summary>
        private float measuredHeight;

        /// <summary>
        /// Measured height of the header row.
        /// </summary>
        private float headerRowHeight;

        /// <summary>
        /// Measure width.
        /// </summary>
        private float measuredWidth;

        /// <summary>
        /// Measured width of each column.
        /// </summary>
        private float[] columnsWidth;

        /// <summary>
        /// Represents the entry for this panel. It will not be null, empty or contain null elements.
        /// Set in the contrustor and will never change.
        /// </summary>
        /// <value>The entry for this panel.</value>
        protected IList<CalendarEntry> Entries
        {
            get
            {
                return entries;
            }
        }

        /// <summary>
        /// Represents the names of columns to display. A null, by default, means all available columns will be used
        /// as they are found in the entry. An empty list means no columns will be displayed, and any given list will
        /// indicate the desired columns. If a given column is not found in the entry, it is ignored.
        /// </summary>
        /// <value>The names of columns to display.</value>
        /// <exception cref="ArgumentException">If set to a list with null/empty strings.</exception>
        public IList<string> Columns
        {
            get
            {
                return Helper.GetStringListCopy(columns);
            }
            set
            {
                Helper.CheckStringListForNullAndEmptyElements(value, "Columns");
                columns = Helper.GetStringListCopy(value);
            }
        }

        /// <summary>
        /// Creates a new instance of this class with the given parameters.
        /// </summary>
        /// <param name="entries">The entries for this panel.</param>
        /// <summary>
        /// Creates new instance with the given params, which are just set to the fields.
        /// Set empty <paramref name="destinationViewName"/> to null.
        /// </summary>
        /// <param name="calendarView">The parent calendar view.</param>
        /// <param name="destinationViewName">The destination view's name
        /// for the calendar view control.</param>
        /// <param name="dateOfInterest">The date around which to wrap the destination view.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="entries"/>
        /// or <paramref name="calendarView"/> is null.</exception>
        /// <exception cref="ArgumentException">If <paramref name="entries"/> is empty
        /// or containes null element.</exception>
        public FullDayInfoPopupPanel(IList<CalendarEntry> entries, AbstractCalendarView calendarView,
                                     string destinationViewName, DateTime dateOfInterest)
            : base(calendarView, destinationViewName, dateOfInterest)
        {
            if (entries == null)
            {
                throw new ArgumentNullException("entries", "Argument entries should not be null.");
            }
            if (entries.Count == 0)
            {
                throw new ArgumentException("Argument entries should not be empty collection.", "entries");
            }
            if (entries.Contains(null))
            {
                throw new ArgumentException("Argument entries should not contain null element.", "entries");
            }

            this.entries = entries;
            sortedEntries = Helper.GroupEntriesByType(entries);
        }

        /// <summary>
        /// Processes the display panel paint event.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event, containing the graphic.</param>
        protected override void OnPopupPaint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            RectangleF cellRect = new RectangleF();
            cellRect.Y = DailyCalendarView.PADDING_TOP;

            foreach (CalendarEntryType entryType in sortedEntries.Keys)
            {
                // The "Type" row:
                // The background color:
                g.FillRectangle(new SolidBrush(CalendarView.GetColor(entryType)), 0,
                                cellRect.Y - DailyCalendarView.PADDING_TOP, measuredWidth, measuredHeight);
                // The string:
                cellRect.X = DailyCalendarView.PADDING_LEFT;
                cellRect.Width = measuredWidth - DailyCalendarView.PADDING_RIGHT;
                cellRect.Height = measuredHeight - DailyCalendarView.PADDING_BOTTOM;
                g.DrawString(entryType.Description, DailyCalendarView.typeRowFont, DailyCalendarView.typeRowBrush,
                             cellRect, Helper.StringTrimFormat);
                cellRect.Y += measuredHeight;

                // The header row:
                // The background color:
                g.FillRectangle(DailyCalendarView.headerBackBrush, 0, cellRect.Y - DailyCalendarView.PADDING_TOP,
                                measuredWidth, headerRowHeight);
                // The header strings:
                cellRect.X = DailyCalendarView.PADDING_LEFT;
                cellRect.Height = headerRowHeight - DailyCalendarView.PADDING_BOTTOM;
                for (int columnIndex = 0; columnIndex < columnsWidth.Length; columnIndex++)
                {
                    cellRect.Width = columnsWidth[columnIndex];
                    g.DrawString(viewColumns[columnIndex], DailyCalendarView.headerFont, DailyCalendarView.headerBrush,
                                 cellRect, Helper.StringTrimFormat);
                    cellRect.X += columnsWidth[columnIndex] + DailyCalendarView.PADDING_RIGHT +
                                  DailyCalendarView.PADDING_LEFT;
                }
                cellRect.Y += headerRowHeight;

                // The entries rows:
                cellRect.Height = measuredHeight - DailyCalendarView.PADDING_BOTTOM;
                foreach (CalendarEntry entry in sortedEntries[entryType])
                {
                    cellRect.X = DailyCalendarView.PADDING_LEFT;
                    for (int columnIndex = 0; columnIndex < columnsWidth.Length; columnIndex++)
                    {
                        cellRect.Width = columnsWidth[columnIndex];

                        g.DrawRectangle(DailyCalendarView.itemBorderPen,
                                        cellRect.X - DailyCalendarView.PADDING_LEFT,
                                        cellRect.Y - DailyCalendarView.PADDING_TOP,
                                        cellRect.Width + DailyCalendarView.PADDING_LEFT
                                        + DailyCalendarView.PADDING_RIGHT,
                                        measuredHeight - 1);

                        g.DrawString(DailyCalendarView.GetColumnValue(entry, viewColumns[columnIndex]),
                                     DailyCalendarView.itemFont,
                                     DailyCalendarView.itemBrush, cellRect,
                                     Helper.StringTrimFormat);

                        cellRect.X += columnsWidth[columnIndex] + DailyCalendarView.PADDING_RIGHT +
                                      DailyCalendarView.PADDING_LEFT;
                    }

                    cellRect.Y += measuredHeight;
                }
            }
        }

        /// <summary>
        /// <para>Displays the popup on the screen.</para>
        /// </summary>
        /// <param name="display">The panel that will hold all drawn data.</param>
        /// <param name="popup">The popup control that holds the panel data.</param>
        protected override void DrawPanel(Panel display, ToolStripDropDown popup)
        {
            using(Graphics g = display.CreateGraphics())
            {
                if (columns == null)
                {
                    viewColumns = DailyCalendarView.defaultColumns;
                }
                else
                {
                    viewColumns = columns;
                }

                headerRowHeight = 0;
                columnsWidth = new float[viewColumns.Count];

                // Calculate the size of the header row.
                for (int columnIndex = 0; columnIndex < columnsWidth.Length; columnIndex++)
                {
                    SizeF cellSize = g.MeasureString(viewColumns[columnIndex], DailyCalendarView.headerFont);
                    columnsWidth[columnIndex] = cellSize.Width;
                    headerRowHeight = Math.Max(headerRowHeight, cellSize.Height);
                }

                measuredWidth = 0;
                measuredHeight = 0;
                int rowCount = 0;
                int headerRowCount = 0;
                foreach (CalendarEntryType entryType in sortedEntries.Keys)
                {
                    // Measure the size of the colored type row.
                    SizeF typeRowSize = g.MeasureString(entryType.Description, DailyCalendarView.typeRowFont);

                    measuredWidth = Math.Max(typeRowSize.Width, measuredWidth);
                    measuredHeight = Math.Max(measuredHeight, typeRowSize.Height);
                    headerRowCount += 1;
                    rowCount += 1;

                    foreach (CalendarEntry entry in sortedEntries[entryType])
                    {
                        rowCount++;

                        // Measure the size of the item cells.
                        for (int columnIndex = 0; columnIndex < columnsWidth.Length; columnIndex++)
                        {
                            SizeF cellSize =
                                g.MeasureString(DailyCalendarView.GetColumnValue(entry, viewColumns[columnIndex]),
                                                DailyCalendarView.itemFont);

                            columnsWidth[columnIndex] = Math.Max(columnsWidth[columnIndex], cellSize.Width);
                            measuredHeight = Math.Max(measuredHeight, cellSize.Height);
                        }
                    }
                }

                float columnsWidthSum = 0;
                foreach (float width in columnsWidth)
                {
                    columnsWidthSum += width;
                }

                measuredWidth = Math.Max(measuredWidth, columnsWidthSum);

                // Add the paddings:
                measuredWidth += (DailyCalendarView.PADDING_LEFT + DailyCalendarView.PADDING_RIGHT)
                                 *viewColumns.Count;
                measuredHeight += DailyCalendarView.PADDING_TOP + DailyCalendarView.PADDING_BOTTOM;
                headerRowHeight += DailyCalendarView.PADDING_TOP + DailyCalendarView.PADDING_BOTTOM;

                float totalHeight = measuredHeight * rowCount + headerRowHeight * headerRowCount;

                // Adjust the size of the popup panel
                ShowPopup((int)Math.Ceiling(measuredWidth), (int)Math.Ceiling(totalHeight));
            }
        }
    }
}